#include "Lexico.h"
#include <iomanip>

Lexico::Lexico()
{
	mNumbers = "0123456789";
	mAlphas  = "abcdefghijklmnopqrstuvxywzABCDEFGHIJKLMNOPQRSTUVXYWZ";
	mSymbols = "()[]{}<>+-*/=:;.,";
	mPalavrasReservadas.push_back("true");
	mPalavrasReservadas.push_back("false");
	mPalavrasReservadas.push_back("procedure");
	mPalavrasReservadas.push_back("function");
	mPalavrasReservadas.push_back("begin");
	mPalavrasReservadas.push_back("end");
	mPalavrasReservadas.push_back("if");
	mPalavrasReservadas.push_back("else");
	mPalavrasReservadas.push_back("than");
	mPalavrasReservadas.push_back("do");
	mPalavrasReservadas.push_back("while");
	mPalavrasReservadas.push_back("type");
	mPalavrasReservadas.push_back("array");
	mPalavrasReservadas.push_back("var");
	mPalavrasReservadas.push_back("program");
	mPalavrasReservadas.push_back("label");
	mPalavrasReservadas.push_back("of");
	mPalavrasReservadas.push_back("or");
	mPalavrasReservadas.push_back("div");
	mPalavrasReservadas.push_back("not");
	mPalavrasReservadas.push_back("integer");
	mPalavrasReservadas.push_back("boolean");
}

Lexico::~Lexico()
{
	mFile.close();
}

std::list<TToken *> Lexico::getTokens()
{
    return this->mTokens;
}

void Lexico::exibirTokens(std::ostream& saida)
{
	saida << "\n                    A N A L I S A D O R   L E X I C O                      \n\n";

	std::list<TToken *>::iterator iter;

    saida << "  ------------------------------------------------------------------------ \n";
    saida << " |                TOKEN                  |   LINE   |        TYPE         |\n";
    saida << "  ------------------------------------------------------------------------ \n";

	for (iter=mTokens.begin(); iter != mTokens.end(); iter++)
    {
        int numCaracter = strlen ((*iter)->token);
       	saida << " | " << (*iter)->token;
       	for (int i = numCaracter; i < 37; i++) saida << " ";
       	saida << " | ";

        if ((*iter)->line >= 10000)
        {
           saida << " " << (*iter)->line << "   | ";
        }
        else if ((*iter)->line >= 1000)
        {
           saida << "  " << (*iter)->line << "   | ";
        }
        else if ((*iter)->line >= 100)
        {
           saida << "  " << (*iter)->line << "    | ";
        }
        else if ((*iter)->line >= 10)
        {
           saida << "   " << (*iter)->line << "    | ";
        }
        else
        {
            saida << "   " << (*iter)->line << "     | ";
        }

       	switch ((*iter)->type)
       	{
       	    case Inteiro:          saida << "Inteiro             |\n"; break;
       	    case Identificador:    saida << "Identificador       |\n"; break;
       	    case Booleano:         saida << "Booleano            |\n"; break;
       	    case PalavraReservada: saida << "PalavraReservada    |\n"; break;
       	    case SimboloSimples:   saida << "SimboloSimples      |\n"; break;
       	    case SimboloComposto:  saida << "SimboloComposto     |\n"; break;
       	    case Erro:             saida << "Erro                |\n"; break;
       	}
	}
    saida << "  ------------------------------------------------------------------------ \n";
    saida << " | Total de tokens: ";
    saida.setf(std::ios::left, std::ios::adjustfield);
    saida << std::setw(5) << mTokens.size() << "                                                 | \n";
    saida << "  ------------------------------------------------------------------------ \n";
}

void Lexico::setFile(char *rFile)
{
	mFile.open(rFile);
}

bool Lexico::isPalavraReservada(char *pPalavra)
{
	std::list<char *>::iterator iter;

	for (iter = mPalavrasReservadas.begin(); iter != mPalavrasReservadas.end(); iter++)
    {
       	if (strcmp (*iter, pPalavra) == 0) return true;
	}
	return false;
}

bool Lexico::isBooleano(char *pPalavra)
{
 	if (strcmp ("true", pPalavra) == 0 || strcmp ("false", pPalavra) == 0) return true;

	return false;
}

bool Lexico::isSimboloComposto(char pCaracter1, char pCaracter2)
{
    if ((pCaracter1 == '<' || pCaracter1 == '>' || pCaracter1 == ':') && pCaracter2 == '=')
    {
        return true;
    }
    else if (pCaracter1 == '<' && pCaracter2 == '>')
    {
        return true;
    }
    return false;
}

bool Lexico::isFimComentario(char pCaracter)
{
    if (pCaracter == '}')
    {
        return true;
    }
    return false;
}

bool Lexico::isFimComentario(char pCaracter1, char pCaracter2)
{
    if (pCaracter1 == '*' && pCaracter2 == ')')
    {
        return true;
    }
    return false;
}

bool Lexico::isInicioComentario(char pCaracter)
{
    if (pCaracter == '{')
    {
        return true;
    }
    return false;
}

bool Lexico::isInicioComentario(char pCaracter1, char pCaracter2)
{
    if (pCaracter1 == '(' && pCaracter2 == '*')
    {
        return true;
    }
    return false;
}


bool Lexico::isAlpha(char pCaracter)
{
	const char *alpha = mAlphas.c_str ();
	for (int i = 0; i < mAlphas.length(); i++)
	{
		if (alpha[i] == pCaracter) return true;
	}
	return false;
}

bool Lexico::isNumber(char pCaracter)
{
	const char *number = mNumbers.c_str ();
	for (int i = 0; i < mNumbers.length(); i++)
	{
		if (number[i] == pCaracter) return true;
	}
	return false;
}


bool Lexico::isSymbol(char pCaracter)
{
	const char *symbol = mSymbols.c_str ();
	for (int i = 0; i < mSymbols.length(); i++)
	{
		if (symbol[i] == pCaracter) return true;
	}
	return false;
}

int Lexico::checkCaracter(char pCaracter)
{
	if (isAlpha  (pCaracter) == true)          return Alpha;
	if (isNumber (pCaracter) == true)          return Number;
	if (isSymbol (pCaracter) == true)          return Symbol;
    if (pCaracter == '\t' || pCaracter == ' ') return Space;
	return Unknown;
}

bool Lexico::analisar()
{
    char        linha[256];
    char        buffer[256];
    int         linhaAtual = 0;
    int         index;

    bool        erroEncontrado = false;
    bool        alpha;
    bool        number;
    bool        identificador;
    bool        inteiro;
    bool        erro;
    bool        comentarioSimples  = false;
    bool        comentarioComposto = false;

    std::string linhaStr;
    char        *palavra;
    TToken      *token;
    Token   type;
    // enquanto nao chegar no fim de arquivo
    while (mFile.eof() == false)
    {
        // atualiza linha atual
        linhaAtual++;

        // inicializa index
        index  = 0;

        // inicializa flags
        identificador   = false;
        number          = false;
        alpha           = false;
        erro            = false;
        inteiro         = false;

        // obtem a linha atual do arquivo
        mFile.getline (linha, 256);

        // atribui linha atual a std::string linha
        linhaStr.assign(linha);

        // percorre todos os caracteres da linha
        for (int i = 0; i < linhaStr.length(); i++)
        {
            // verifica o tipo do caracter atual
            switch (checkCaracter(linha[i]))
            {
                // caso seja um caracter alfa numerico
                case Alpha:
                    // verifica se o estado atual eh de comentario
     			    if (comentarioSimples == true || comentarioComposto == true) break;
     			    // seta flag de alpha
    			    alpha = true;
    			    // obtem o caracter atual do linha
                    buffer[index++]  = linha[i];
                    // caso nao tenha sido lido um caracter de numero ainda
    			    if (number == false)
    			    {
    			        // seta flag de identificador
    			        identificador = true;
    			    }
    			    // caso ja tenha sido lido um caracter de numero e ainda nao tenha lido um alpha
                    else if (identificador == false)
                    {
                        // seta flag de erro, pois o identificador esta comecando com numero
                        erro = true;
                        erroEncontrado = true;
                    }
                    break;

                // caso seja um numero
                case Number:
                    // verifica se o estado atual eh de comentario
                    if (comentarioSimples == true || comentarioComposto == true) break;
                    // seta flag de numero
                    number = true;
                    // obtem o caracter atual
                    buffer[index++] = linha[i];
                    // caso ainda nao tenha sido lido um alpha, seta flag de inteiro
                    if (alpha == false) inteiro = true;
                    break;
    			
    			// caso seja um simbolo
    			case Symbol:
        			// verifica se o estado atual eh de comentario
     			    if (comentarioSimples == true || comentarioComposto == true)
     			    {
     			        // verifica se estado atual eh de comentario simples
     			        if (comentarioSimples == true)
     			        {
    			            index = 0;
    			            if (isFimComentario (linha[i]) == true)
    			            {
                                // cria um token e adiciona na lista
                                //createToken ("}", SimboloSimples, linhaAtual);
    			                comentarioSimples = false;
    			            }
     			        }
     			        // verifica se existe um proximo caracter que pode ser um comentario composto
     			        else if (i + 1 < linhaStr.length())
     			        {
                            index = 0;
                            if (isFimComentario (linha[i], linha[i+1]) == true)
                            {
                                //createToken ("*)", SimboloComposto, linhaAtual);
                                comentarioComposto = false;
                                i++;
                            }
     			        }
     			    }
     			    // caso nao esteja em estado de comentario
     			    else
     			    {
     			        // verifica se existe algo no buffer
     			        if (index > 0)
     			        {
                            // verifica se eh um identificador ou numero
                            if ((identificador == true || inteiro == true))
                            {
                                buffer[index] = '\0';
            			        if (inteiro == true)
                                    type = Inteiro;
                                else if (isBooleano(buffer) == true)
                                    type = Booleano;
                                else if (isPalavraReservada(buffer) == true)
                                    type = PalavraReservada;
                                else
                                    type = Identificador;

            			        createToken (buffer, type, linhaAtual);
                            }
                        }
     			        // verifica se eh inicio de comentario simples
                        if (isInicioComentario (linha[i]) == true)
                        {
          			        //createToken ("{", SimboloSimples, linhaAtual);
                            comentarioSimples = true;
                        }
                        // verifica se existe um proximo caracter que pode ser um comentario composto
                        else if (i + 1 < linhaStr.length())
                        {
                            if (isInicioComentario (linha[i], linha[i+1]) == true)
                            {
              			        //createToken ("(*", SimboloComposto, linhaAtual);
                                comentarioComposto = true;
               			    }
                        }
                        // caso nao tenha sido inicio de comentario guarda o simbolosimples
                        if (comentarioSimples == false && comentarioComposto == false)
                        {
                            bool simboloComposto = false;
                            // verifica se eh um simbolo composto
                            if (i + 1 < linhaStr.length())
                            {
                                if (isSimboloComposto (linha[i], linha[i+1]) == true)
                                {
                                    buffer[0] = linha[i];
                                    buffer[1] = linha[i+1];
                                    buffer[2] = '\0';
                                    simboloComposto = true;
                                    createToken (buffer, SimboloComposto, linhaAtual);
                                    i++;
                                }
                            }
                            if (simboloComposto == false)
                            {
                                buffer[0] = linha[i];
                                buffer[1] = '\0';
                                createToken (buffer, SimboloSimples, linhaAtual);
                            }
                        }
                    }
       			    index = 0;
        			number = alpha = inteiro = identificador = erro = false;
        			break;

                // caso seja um espaco
    			case Space:
        			// verifica se o estado atual eh de comentario
     			    if (comentarioSimples == true || comentarioComposto == true) break;
                    // verifica se existe algum caracter no buffer
     			    if (index > 0)
     			    {
     			        // seta fim de string
         			    buffer[index] = '\0';

                        // verifica se eh do tipo identificador
                        if (identificador == true)
                        {
                            if (isBooleano(buffer) == true)
                               type = Booleano;
                            else
                            {
                                // verifica se eh palavra reservada
                                type = (isPalavraReservada(buffer) == true ? PalavraReservada : Identificador);
                            }
                        }
                        else if (inteiro == true)
                        {
                            // verifica se eh um numero valido
                            type = (erro == true ? Erro : Inteiro);
                        }

                        if (erro == true) erroEncontrado = true;

                        // cria um token e adiciona na lista
     			        createToken (buffer, type, linhaAtual);
     			    }
     			    // zera as flags
    			    index = 0;
    			    number = alpha = inteiro = identificador = erro = false;
    				break;

                // caso seja um caracter desconhecido
    			case Unknown:
        			// verifica se o estado atual eh de comentario
     			    if (comentarioSimples == true || comentarioComposto == true) break;
     			    
     			    erroEncontrado = true;
     			    
                    // verifica se existe algum caracter no buffer para guarda-lo
     			    if (index > 0)
     			    {
     			        // seta fim de string
         			    buffer[index] = '\0';

                        // verifica se eh do tipo identificador
                        if (identificador == true)
                        {
                            // verifica se eh palavra reservada
                            type = (isPalavraReservada(buffer) == true ? PalavraReservada : Identificador);
                        }
                        else if (inteiro == true)
                        {
                            // verifica se eh um numero valido
                            type = (erro == true ? Erro : Inteiro);
                        }

                        // cria um token e adiciona na lista
     			        createToken (buffer, type, linhaAtual);
     			    }
    			    
    			    // aloca uma string para o caracter desconhecido e adiciona na lista de tokens
                    char *s      = new char [2];
                    s[0]         = linha[i];
                    s[1]         = '\0';
   			        createToken (s, Erro, linhaAtual);
    			    index = 0;
    			    number = alpha = inteiro = identificador = erro = false;
    				break;
    		}
    	} // for (int i = 0; i < linhaStr.length(); i++)

    	if (comentarioSimples == false && comentarioComposto == false)
    	{
		    if (index > 0)
		    {
    		    buffer[index] = '\0';
                if (identificador == true)
                {
                    if (isPalavraReservada(buffer) == true)
                        type = PalavraReservada;
                    else
                        type = Identificador;
                }
                else if (inteiro == true)
                {
                    if (erro == true)
                    {
                        type           = Erro;
                        erroEncontrado = true;
                    }
                    else
                    {
                        type = Inteiro;
                    }
                }
		        createToken (buffer, type, linhaAtual);
		    }
		}
        
	} // while (mFile.eof() == false)
	
	return !erroEncontrado;
}

void Lexico::createToken (char *pToken, Token pType, int pLinha)
{
    TToken *auxToken = new TToken();
    auxToken->token  = new char [strlen (pToken) + 1];
    auxToken->line   = pLinha;
    auxToken->type   = pType;
    strcpy (auxToken->token, pToken);
    
    mTokens.push_back (auxToken);
}
